﻿namespace Refunctor

open System
open System.Text
open Reflector
open Reflector.CodeModel
open Reflector.CodeModel.Memory
open System.Windows
open System.Windows.Controls
open System.Windows.Input
open System.Windows.Data
open System.Windows.Media
open System.Windows.Media.Imaging
open Microsoft.Win32
open System.IO
open ICSharpCode.AvalonEdit
open System.Reflection
open ICSharpCode.AvalonEdit.Document
open ICSharpCode.AvalonEdit.Editing
open System.Xml
open System.Xml.Linq
open System.Runtime.InteropServices
open System.Text.RegularExpressions
open System.Windows.Markup

[<RequireQualifiedAccess>]
module FileUtility = (* The code of FileUtility Module is largely based on SharpDevelop's FileUtility class code  *)
        
        [<Literal>]
        let MaxPathLength = 260
        
        [<Literal>]
        let FileNameRegEx = @"^([a-zA-Z]:)?[^:]+$"

        let (|HasOSRestrictedName|) fileName =
            let nameWithoutExt = Path.GetFileNameWithoutExtension(fileName).ToUpperInvariant()   
            let ch = if nameWithoutExt.Length = 4 then nameWithoutExt.[3] else '_'                             
            ["CON";"PRN";"AUX";"NUL"] |> List.exists ((=) nameWithoutExt) ||
            ((nameWithoutExt.StartsWith("COM") || nameWithoutExt.StartsWith("LPT")) && Char.IsDigit(ch))
        
        let isValidFileName fileName =                                                                                       
            match fileName with
            | fn when String.IsNullOrWhiteSpace(fn) || String.IsNullOrWhiteSpace(Path.GetFileNameWithoutExtension(fn)) ||
                      fn.IndexOfAny(Path.GetInvalidFileNameChars()) >= 0 ||                      
                      not <| Regex.IsMatch(fn, FileNameRegEx) -> false
            | HasOSRestrictedName true -> false                       
            | _ -> true

        let isValidPath folderPath = (* check full folder path *)
            match folderPath with
            | fp when String.IsNullOrWhiteSpace(fp) ||
                      fp.IndexOfAny(Path.GetInvalidPathChars()) >= 0 ||
                      not <| Directory.Exists(folderPath) -> false                                             
            | _ -> true        

[<RequireQualifiedAccess>]
module RemoteController = (* The RemoteController module code is entirely based on the work of Jason Haley http://jasonhaley.com/blog/post/2009/01/30/ReflectorSnippy-Addin-updated-(v0930).aspx *)
    
    type EnumWindowsCallback = delegate of nativeint * int -> bool

    [<Struct;StructLayout(LayoutKind.Sequential)>]
    type CopyDataStruct =               
        new (p,s,b) = {Padding = p; Size = s; Buffer = b}
        val mutable Padding : nativeint
        val mutable Size : int
        val mutable Buffer : nativeint        
                
    [<DllImport("user32.dll")>]
    extern int  EnumWindows(EnumWindowsCallback callback, int lparam) 
    [<DllImport("user32.dll")>]
    extern int GetWindowText(nativeint hWnd, StringBuilder title, int size)
    [<DllImport("user32.dll")>]
    extern bool SendMessage(nativeint hWnd, int Msg, nativeint wParam, CopyDataStruct* lParam)

    let mutable targetWindow : nativeint = IntPtr.Zero

    let enumWindow (handle : nativeint, lparam : int) : bool =
        let title = new StringBuilder(0x100)
        GetWindowText(handle,title,0x100) |> ignore
        if title.ToString().StartsWith("Red Gate's .NET Reflector") then targetWindow <- handle            
        true

    let sendCopyDataMessage (msg : string) =
        targetWindow <- IntPtr.Zero
        EnumWindows(EnumWindowsCallback(fun h l -> enumWindow(h,l)), 0) |> ignore
        let mutable flag = false
        if targetWindow <> IntPtr.Zero then 
            let charArray = msg.ToCharArray()
            let mutable lParam = CopyDataStruct()
            lParam.Padding <- IntPtr.Zero
            lParam.Size <- int (charArray.Length * 2)   
            lParam.Buffer <- Marshal.AllocHGlobal(lParam.Size)         
            Marshal.Copy(charArray, 0, lParam.Buffer, int charArray.Length)
            flag <- SendMessage(targetWindow, 0x4a, IntPtr.Zero, &&lParam)
            Marshal.FreeHGlobal(lParam.Buffer)
        flag

    let select (str : string) =
        sendCopyDataMessage ("Select\n" + str)

[<AutoOpen>]
module Reflection =
    (*-: ref : Expert F# 2nd Edition Page 243 :-*) 
    let (?) (o:obj) (name:string) : 'T =
        o.GetType().InvokeMember(name, BindingFlags.GetProperty, null, o, [||]) |> unbox<'T>
    let (?<-) (o:obj) (name:string) (v:obj) : unit =
        o.GetType().InvokeMember(name, BindingFlags.SetProperty, null, o, [|v|]) |> ignore
 
[<AutoOpen>]
module MessageService =    
    let showMessage(text,caption) =
        MessageBox.Show(text,caption,MessageBoxButton.OK,MessageBoxImage.Information) |> ignore

    let showError(text) =
        MessageBox.Show(text, "Error", MessageBoxButton.OK, MessageBoxImage.Error) |> ignore

    let showWarning(text) =
        MessageBox.Show(text, "Warning" ,MessageBoxButton.OK,MessageBoxImage.Warning) |> ignore

    let askQuestion(text,caption) = 
        MessageBox.Show(text, caption, MessageBoxButton.YesNoCancel, MessageBoxImage.Question)

[<AutoOpen>]
module Commands =
    type RefunctorCommands() =                    
        static let mutable clearAllCommand = new RoutedUICommand("Clear All", "Clear All", typeof<RefunctorCommands>)  
        static let mutable resetSessionCommand = new RoutedUICommand("Reset Session", "Reset Session", typeof<RefunctorCommands>) 
        static let mutable loadScriptFileCommand = new RoutedUICommand("Load", "Load F# Script File", typeof<RefunctorCommands>) 
        static let mutable clearHistoryCommand = new RoutedUICommand("Clear History", "Clear History", typeof<RefunctorCommands>) 
        static let mutable optionsCommand = new RoutedUICommand("Options", "Options", typeof<RefunctorCommands>)
        static let mutable buildCommand = new RoutedUICommand("Build", "Build", typeof<RefunctorCommands>,
                                              InputGestureCollection([|KeyGesture(Key.B, ModifierKeys.Control ||| ModifierKeys.Shift)|]))
        static let mutable runCommand = new RoutedUICommand("Run", "Run", typeof<RefunctorCommands>,
                                            InputGestureCollection([|KeyGesture(Key.F5) |]))
        static let mutable addAsmRefCommand = new RoutedUICommand("Assembly References", "Assembly References", typeof<RefunctorCommands>)
        static member ClearAllCommand = clearAllCommand
        static member ResetSessionCommand = resetSessionCommand
        static member LoadScriptFileCommand = loadScriptFileCommand 
        static member ClearHistoryCommand = clearHistoryCommand
        static member OptionsCommand = optionsCommand
        static member BuildCommand = buildCommand
        static member RunCommand = runCommand
        static member AddAsmRefCommand = addAsmRefCommand    

[<AutoOpen>]
module UI =   
  
    type DialogResult =
        | OK        = 0
        | Cancel    = 1 
        | Reset     = 2

    [<Literal>]
    let REFUNCTOR_WINDOW_ID = "Refunctor"
      
    let makeBrush hex = (BrushConverter()).ConvertFromString(hex) :?> SolidColorBrush 
    let toolBarBrush = makeBrush "#BCC7D8"
    let separatorBrush = makeBrush "#2B3C59"

    type InvokerItemType<'a when 'a :> ICommand> =
                | CommandItem of (string * string(* ToolTip for Toolbar Item, InputGestureText for ContextMenuItem *) * 'a)               
                | Separator    
                                  
    let buildToolbar (items : InvokerItemType<_> array) =            
        let createButton (item : InvokerItemType<_>) = 
            match item with
            | Separator -> new Separator(Background = separatorBrush) :> Control
            | CommandItem(name, tooltip, cmd) ->
                let button = new Button(VerticalAlignment = VerticalAlignment.Center)
                button.Command <- cmd
                ToolTipService.SetToolTip(button,ToolTip(Content=tooltip))
                ToolTipService.SetShowOnDisabled(button,true)                        
                button.Content <- new Image(Source = BitmapImage(Uri("pack://application:,,,/refunctor;component/images/" + name + ".png")), Stretch = Stretch.Uniform, Height=16., Width = 16.)
                button :> Control               
        let toolbar =
            items |> Array.fold (fun (acc:ToolBar) item -> acc.Items.Add(createButton item) |> ignore; acc)
                                (new ToolBar(Background = toolBarBrush, Height = 33.))
        toolbar

    let buildContextMenu (items : InvokerItemType<_> array) =
        let createMenuItem (item : InvokerItemType<_>) =
            match item with
            | Separator -> new Separator() :> Control
            | CommandItem(header, key, cmd) ->
                let item = new MenuItem()
                item.Header <- header
                item.Command <- cmd
                item.InputGestureText <- key
                item.Icon <- new Image(Source = BitmapImage(Uri("pack://application:,,,/refunctor;component/images/" + header + ".png")), Stretch = Stretch.Uniform, Height=16., Width = 16.)
                item :> Control
        let contextMenu = 
            items |> Array.fold (fun (acc:ContextMenu) item -> acc.Items.Add(createMenuItem item) |> ignore; acc)
                                (new ContextMenu())
        contextMenu

    let getFolderPath() =
            let dialog = new System.Windows.Forms.FolderBrowserDialog()
            dialog.ShowNewFolderButton <- true
            dialog.Description <- "Please choose a folder to save file."
            let r = dialog.ShowDialog()
            if r = System.Windows.Forms.DialogResult.OK then Some(dialog.SelectedPath)                
            else None
                    
    type BeginReadOnlySectionProvider() =
        let mutable endOfOffset = 0
        member self.EndOffset
            with get() =  endOfOffset
            and set v = endOfOffset <- v
        interface IReadOnlySectionProvider with
            member self.CanInsert offset = offset >= self.EndOffset
            member self.GetDeletableSegments (segment : ISegment) =
                if segment.EndOffset < self.EndOffset then Seq.empty<ISegment>
                else
                    Seq.singleton ((new TextSegment(StartOffset = System.Math.Max(self.EndOffset, segment.Offset),
                                        EndOffset = segment.EndOffset)) :> ISegment)
                     
    let getEditor() = 
        let editor = new ICSharpCode.AvalonEdit.TextEditor(                       
                        SyntaxHighlighting= Highlighting.HighlightingManager.Instance.GetDefinition("F#"),     
                        HorizontalScrollBarVisibility = ScrollBarVisibility.Auto, VerticalScrollBarVisibility = ScrollBarVisibility.Auto,                    
                        FontFamily = new FontFamily("Consolas"), Padding = Thickness(5.),
                        FontSize = 14., IsTabStop = false,
                        FontWeight = FontWeights.Normal)                        
        editor.Options.ConvertTabsToSpaces <- true
        editor.Options.EnableHyperlinks <- true
        editor.Options.EnableEmailHyperlinks <- true
        editor.Options.RequireControlModifierForHyperlinkClick <- false          
        editor.Options.CutCopyWholeLine <- false
        editor.WordWrap <- true
        editor.TextArea.ReadOnlySectionProvider <- new BeginReadOnlySectionProvider()
        editor
    
    type OutputWindow() as self =
        inherit ContentControl(Visibility = Visibility.Collapsed, HorizontalAlignment = HorizontalAlignment.Stretch, VerticalAlignment = VerticalAlignment.Stretch)

        let headerLbl = new Label(Content = "Output", Height = 27., FontWeight = FontWeights.Normal, VerticalAlignment = VerticalAlignment.Top, VerticalContentAlignment = VerticalAlignment.Top)        
        let closeButton = new Button(Height = 20., VerticalAlignment = VerticalAlignment.Center, HorizontalAlignment = HorizontalAlignment.Right, Margin = Thickness(0.,0.,5.,0.))
        let outputTextBox = new TextBox(HorizontalAlignment = HorizontalAlignment.Stretch, VerticalAlignment = VerticalAlignment.Stretch, IsReadOnly = true, SnapsToDevicePixels = true,
                                        TextWrapping = TextWrapping.Wrap, HorizontalScrollBarVisibility = ScrollBarVisibility.Auto, VerticalScrollBarVisibility = ScrollBarVisibility.Auto)                
        do
            let grid = new Grid()
            grid.RowDefinitions.Add(RowDefinition(Height = GridLength.Auto))
            grid.RowDefinitions.Add(RowDefinition(Height = GridLength(1.,GridUnitType.Star)))
            closeButton.Content <- new Image(Source = BitmapImage(Uri("pack://application:,,,/refunctor;component/images/window-close.png")), Stretch = Stretch.Uniform, Height=16., Width = 16.)
            closeButton.Click.Add(fun _ -> self.Visibility <- Visibility.Collapsed; self.Height <- 0.0)
            let dockPanel = new DockPanel(Background = toolBarBrush, HorizontalAlignment = HorizontalAlignment.Stretch, VerticalAlignment = VerticalAlignment.Stretch )
            DockPanel.SetDock(headerLbl, Dock.Left)
            DockPanel.SetDock(closeButton, Dock.Right)
            dockPanel.Children.Add(headerLbl) |> ignore
            dockPanel.Children.Add(closeButton) |> ignore
            Grid.SetRow(dockPanel,0)
            grid.Children.Add(dockPanel) |> ignore
            Grid.SetRow(outputTextBox,1)
            grid.Children.Add(outputTextBox) |> ignore
            self.Content <- grid

        member self.SetText text isShown =   
            outputTextBox.IsReadOnly <- false
            outputTextBox.Text <- text
            outputTextBox.IsReadOnly <- true
            self.Height <- 200.0
            if isShown then self.Visibility <- Visibility.Visible                
            else self.Visibility <- Visibility.Collapsed

    type OpenNewFileDialog(folderPath : string) as self =
        inherit Window(Title = "New File", WindowStyle = WindowStyle.ToolWindow, WindowStartupLocation = WindowStartupLocation.CenterScreen, 
                        ShowInTaskbar = false, SnapsToDevicePixels = true, ResizeMode = ResizeMode.NoResize, SizeToContent = SizeToContent.WidthAndHeight, FontSize = 12.)                        
        let fileNameText = new TextBox(MaxLength = 250, Width = 350.0, Height = 25., FontWeight = FontWeights.Normal, TabIndex = 1)
        let folderText = new TextBox(MaxLength = 200, Width = 350.0, Height = 25., FontWeight = FontWeights.Normal, TabIndex = 2, Text = folderPath)
        let browseButton = new Button(Content= "...", Height = 25., Width = 30., Margin = Thickness(5.,0.,0.,0.), FontWeight = FontWeights.Normal, TabIndex = 3)                                           
        let okButton = new Button(Content = "OK", Height = 25., Width = 80., FontWeight = FontWeights.Normal, HorizontalAlignment = HorizontalAlignment.Center, IsDefault = true )
        let cancelButton = new Button(Content = "Cancel", Height = 25., Width = 80., FontWeight = FontWeights.Normal, HorizontalAlignment = HorizontalAlignment.Center, Margin = new Thickness(5.,0.,0.,0.), IsCancel = true)        
        let errorLabel = new Label(FontSize = 10.0, Height = 25., FontWeight = FontWeights.Normal, HorizontalAlignment = HorizontalAlignment.Left, Margin = Thickness(5.,0.,10.,0.), Foreground = Brushes.Red)
        let mutable dialogResult = DialogResult.Cancel

        let generateNewFileName path =
            let defaultFileName = "FSharpProgram"
            if Directory.Exists(path) then
                let nums = Directory.GetFiles(path)
                            |> Array.map (fun x -> Path.GetFileNameWithoutExtension(x))
                            |> Array.filter (fun x -> x.StartsWith(defaultFileName, StringComparison.OrdinalIgnoreCase))
                            |> Array.map (fun x -> 
                                            match Int32.TryParse(x.Substring(defaultFileName.Length)) with
                                            | true, v -> Some(v)
                                            | _ -> None)
                            |> Array.filter (fun x -> Option.isSome x)
                if Array.isEmpty (nums) then "FSharpProgram1"
                else defaultFileName +  (nums |> Array.max |> Option.get |> ((+) 1) |> string) 
            else
                "FSharpProgram1"
        
        do    
            let grid = new Grid(Margin = Thickness(10.))                                 
            grid.RowDefinitions.Add(RowDefinition(Height=GridLength(0.0,GridUnitType.Auto)))
            grid.RowDefinitions.Add(RowDefinition(Height=GridLength(0.0,GridUnitType.Auto)))
            grid.RowDefinitions.Add(RowDefinition(Height=GridLength(0.0,GridUnitType.Auto)))
            grid.RowDefinitions.Add(RowDefinition(Height=GridLength(0.0,GridUnitType.Auto)))
            
            let sp1 = new StackPanel(Orientation = Orientation.Horizontal, Margin = new Thickness(5.,10.,0.,0.))
            Grid.SetRow(sp1,0)            
            sp1.Children.Add(Label(Content="File Name:", Width = 100.0, FontWeight = FontWeights.Normal) :> UIElement) |> ignore
            fileNameText.Text <- generateNewFileName folderPath
            sp1.Children.Add(fileNameText :> UIElement) |> ignore            
            grid.Children.Add(sp1) |> ignore
            
            let sp2 = new StackPanel(Orientation = Orientation.Horizontal, Margin = new Thickness(5.,10.,0.,0.))
            Grid.SetRow(sp2,1)   
            folderText.TextChanged.Add (fun _ -> fileNameText.Text <- generateNewFileName <| folderText.Text.Trim())             
            browseButton.Click.Add(fun _ ->  match getFolderPath() with 
                                             | Some(a) -> folderText.Text <- a                                                                                                
                                             | _ -> ())
            sp2.Children.Add(Label(Content="Location:", Width = 100.0, FontWeight = FontWeights.Normal) :> UIElement) |> ignore
            sp2.Children.Add(folderText :> UIElement) |> ignore                           
            sp2.Children.Add(browseButton :> UIElement) |> ignore        
            grid.Children.Add(sp2) |> ignore   

            let rectangle = new System.Windows.Shapes.Rectangle(StrokeThickness = 1., Stroke = Brushes.Silver, Margin = Thickness(0.,20.,0.,7.))
            Grid.SetRow(rectangle,2)
            grid.Children.Add(rectangle) |> ignore

            let sp3 = new StackPanel(Orientation = Orientation.Horizontal, Margin = new Thickness(5.,0.,0.,0.), HorizontalAlignment = HorizontalAlignment.Right)
            Grid.SetRow(sp3,3)
            okButton.Click.Add(fun _ -> 
                                if not <| FileUtility.isValidFileName(self.FileName)  then
                                    errorLabel.Content <- "Invalid file name!"
                                elif not <| FileUtility.isValidPath(self.Folder) then
                                    errorLabel.Content <- "Invalid location or location doesn't exist!"
                                elif self.FullFileName.Length >= FileUtility.MaxPathLength then
                                    errorLabel.Content <- "File path too long!"
                                else                                     
                                    dialogResult <- DialogResult.OK
                                    self.Close())
            cancelButton.Click.Add(fun _ -> dialogResult <- DialogResult.Cancel)
            sp3.Children.Add(errorLabel) |> ignore
            sp3.Children.Add(okButton) |> ignore
            sp3.Children.Add(cancelButton) |> ignore
            grid.Children.Add(sp3) |> ignore
                        
            self.Content <- grid

        member self.DialogResult with get() = dialogResult
        member self.FileName with get() = match Path.GetExtension(fileNameText.Text.Trim()) with
                                          | ex when ex.Length > 0 -> fileNameText.Text.Trim()
                                          | _ -> fileNameText.Text.Trim() + ".fs"
        member self.Folder with get() = folderText.Text.Trim()
        member self.FullFileName with get() : string = Path.Combine(self.Folder, self.FileName)
    
    type AsmRef = {Name : string ; Location : string}
    
    let asmRefXaml = (* Finally decided to creat UI from Xaml. Creating UI entirely in code is more painful than fun *)
        "<Grid Margin='10' xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation'
            xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
            <Grid.Resources>
                <Style x:Key='DataGridColumnHeaderStyle' TargetType='{x:Type DataGridColumnHeader}'>
                    <Setter Property='Foreground' Value='Black'/>
                    <Setter Property='Background' Value='Silver'/>
                    <Setter Property='FontWeight' Value='SemiBold'/>                    
                </Style>
            </Grid.Resources>
            <Grid.RowDefinitions>
                <RowDefinition Height='*' />
                <RowDefinition Height='Auto' />
                <RowDefinition Height='Auto' />
                <RowDefinition Height='Auto' />
            </Grid.RowDefinitions>
            <DataGrid AutoGenerateColumns='false' HorizontalAlignment='Stretch' Name='asmDataGrid' VerticalAlignment='Stretch' BorderBrush='DarkGray' BorderThickness='1'
                      GridLinesVisibility='None' RowHeaderWidth='0' SelectionUnit='FullRow' ColumnHeaderStyle='{StaticResource DataGridColumnHeaderStyle}'
                      VerticalScrollBarVisibility='Auto' HorizontalScrollBarVisibility='Auto' >
                <DataGrid.Columns>
                    <DataGridTextColumn Binding='{Binding Name}' Header='Name' MinWidth='200' IsReadOnly='True'/>
                    <DataGridTextColumn Binding='{Binding Location}' Header='Location' MinWidth='340' IsReadOnly='True'/>
                </DataGrid.Columns>
            </DataGrid>
            <StackPanel Grid.Row='1' Margin='0 5 0 5' Orientation='Horizontal' Height='40' HorizontalAlignment='Left'>
                <Button Name='addButton' Width='70' Height='25' Content='Add...' IsDefault='True'/>
                <Button Name='removeButton' Width='70' Height='25' Margin='5 0 0 0' Content='Remove'/>
            </StackPanel>
            <Rectangle Grid.Row='2' Stroke='DarkGray' HorizontalAlignment='Stretch' Height='1'/>
            <StackPanel Grid.Row='3' Margin='0 5 0 0' Orientation='Horizontal' Height='30' HorizontalAlignment='Right'>                
                <Button Name='closeButton' Width='70' Height='25' Content='Close' />
            </StackPanel>
        </Grid>"
        
    type AsmRefDialog(asmRefCollection : seq<AsmRef>) as self=
        inherit Window(Title="Assembly Reference", Height=413., Width=550., SnapsToDevicePixels=true,
                       WindowStartupLocation=WindowStartupLocation.CenterScreen, WindowStyle= WindowStyle.ToolWindow)
        
        [<DefaultValue>]
        val mutable container : Grid
        
        [<DefaultValue>]
        val mutable asmDataGrid : DataGrid
        
        [<DefaultValue>]
        val mutable addButton : Button
        
        [<DefaultValue>]
        val mutable removeButton : Button        
        
        [<DefaultValue>]
        val mutable closeButton : Button        

        let asmRefList = new ResizeArray<_>(asmRefCollection)

        let refresh (items : seq<AsmRef>) =
            asmRefList.Clear()
            asmRefList.AddRange(items)
            self.asmDataGrid.ItemsSource <- null
            self.asmDataGrid.ItemsSource <- asmRefList

        do
            self.container <- XamlReader.Parse(asmRefXaml) :?> Grid
            self.asmDataGrid <- self.container.FindName("asmDataGrid") :?> DataGrid
            self.asmDataGrid.ItemsSource <- asmRefList
            self.addButton <- self.container.FindName("addButton") :?> Button
            self.addButton.Click.Add(fun _ -> 
                                        let dialog = new OpenFileDialog()                                        
                                        dialog.Filter <- "dll|*.dll|exe|*.exe"
                                        dialog.RestoreDirectory <- true
                                        dialog.Multiselect <- true
                                        let r = dialog.ShowDialog()
                                        if r.HasValue && r.Value  then
                                           let asmRefs = dialog.FileNames 
                                                         |> Array.map (fun x -> {Name = Path.GetFileNameWithoutExtension(x) ; Location = x})                                                        
                                                         |> Array.toSeq
                                           let allRefs = (Seq.append asmRefs asmRefList) |> Seq.distinct |> Seq.sort |> Seq.toList                                                
                                           refresh allRefs
                                    )
            self.removeButton <- self.container.FindName("removeButton") :?> Button   
            self.removeButton.Command <- ApplicationCommands.Delete         
            self.closeButton <- self.container.FindName("closeButton") :?> Button
            self.closeButton.Click.Add(fun _ -> 
                                        self.asmDataGrid.ItemsSource <- null
                                        self.Close())
            self.Content <- self.container

            let removeButtonCommandBinding = 
                new CommandBinding(ApplicationCommands.Delete,
                    ExecutedRoutedEventHandler(fun s (e:ExecutedRoutedEventArgs ) -> 
                                                let selectedItems = self.asmDataGrid.SelectedItems |> Seq.cast<AsmRef>                                                
                                                let items = Set.difference (asmRefList |> Set.ofSeq) (selectedItems |> Set.ofSeq)
                                                refresh items
                                              ),
                    CanExecuteRoutedEventHandler(fun s (e:CanExecuteRoutedEventArgs ) -> e.CanExecute <- self.asmDataGrid.SelectedItems.Count > 0))                    
            self.CommandBindings.Add(removeButtonCommandBinding) |> ignore

        member self.AsmRefCollection with get() : seq<AsmRef> = asmRefList :> seq<AsmRef>
                                    
[<AutoOpen>]
module Options =         
    type EditorOption = 
        {
             FontName               : string
             FontSize               : float
             FontColor              : Color
             BackgroundColor        : Color
             IdentSize              : float
             WordWrap               : bool
             SyntaxHilighting       : bool
             ConvertTabsToSpaces    : bool
             ShowSpace              : bool
             ShowTabs               : bool
             ShowEndOfLine          : bool
             ShowLineNumber         : bool             
        }    

    let getFont name =
        let f = (fun (x:FontFamily) -> x.Source = name)
        if Fonts.SystemFontFamilies |> Seq.exists f  then
            Fonts.SystemFontFamilies |> Seq.find f
        else Fonts.SystemFontFamilies  |> Seq.nth 0

    let creatEditorElement (option : EditorOption) =
        let editorElement = new XElement(XName.Get("Editor"))
        editorElement.Add(new XElement(XName.Get("FontName"), option.FontName))
        editorElement.Add(new XElement(XName.Get("FontSize"), option.FontSize))
        editorElement.Add(new XElement(XName.Get("FontColor"), option.FontColor.ToString()))
        editorElement.Add(new XElement(XName.Get("BackgroundColor"), option.BackgroundColor.ToString()))
        editorElement.Add(new XElement(XName.Get("IdentSize"), option.IdentSize))
        editorElement.Add(new XElement(XName.Get("WordWrap"), option.WordWrap))
        editorElement.Add(new XElement(XName.Get("SyntaxHilighting"), option.SyntaxHilighting))
        editorElement.Add(new XElement(XName.Get("ConvertTabsToSpaces"), option.ConvertTabsToSpaces))
        editorElement.Add(new XElement(XName.Get("ShowSpace"), option.ShowSpace))
        editorElement.Add(new XElement(XName.Get("ShowTabs"), option.ShowTabs))
        editorElement.Add(new XElement(XName.Get("ShowEndOfLine"), option.ShowEndOfLine))
        editorElement.Add(new XElement(XName.Get("ShowLineNumber"), option.ShowLineNumber))
        editorElement

    (*:- Got the idea from http://fsharpnews.blogspot.com/2011/01/patterns-are-everywhere.html -:*)
    let defaultOrValue (_, Some x | x, None) = x

    let getValue (element:XElement option) (name:string) = 
        match element with
        | Some (ele) ->
            match ele.Elements() |> Seq.tryPick (fun x -> if x.Name.LocalName = name then Some(x) else None ) with
            | Some(a) -> Some(a.Value)
            | _ -> None
        | _ -> None

    let tryParseFloat (str : string option) =
        match str with
        | Some(a) -> 
            match Double.TryParse(a) with
            | true, x -> Some(x)
            | _ -> None
        | _ -> None
            
    let isHex (value : string) = System.Text.RegularExpressions.Regex("^#([A-Fa-f0-9]{8}|[A-Fa-f0-9]{3})$").IsMatch(value)
        
    let tryParseColor (str : string option) =
        match str with
        | Some(a) -> 
            match isHex a with
            | true -> Some(ColorConverter.ConvertFromString(a) :?> Color)
            | _ -> None        
        | _ -> None

    let tryParseBool (str : string option) =
        match str with
        | Some(a) ->
            match Boolean.TryParse(a) with
            | true,x -> Some(x)
            | _ -> None
        | _ -> None
    
    let createEditorOption (rootElement : XElement option) =
        let editorElement = match rootElement with  
                            | Some(a) -> a.Elements() |> Seq.tryPick (fun x -> if x.Name.LocalName = "Editor" then Some(x) else None)
                            | _ -> None  
        {
            FontName = defaultOrValue("Consolas", getValue editorElement "FontName")
            FontSize = defaultOrValue(14.0, tryParseFloat(getValue editorElement "FontSize"))
            FontColor = defaultOrValue (Colors.Black, tryParseColor(getValue editorElement "FontColor"))
            BackgroundColor = defaultOrValue (Colors.White, tryParseColor(getValue editorElement "BackgroundColor"))
            IdentSize = defaultOrValue(4.0, tryParseFloat(getValue editorElement "IdentSize"))
            WordWrap = defaultOrValue(true, tryParseBool(getValue editorElement "WordWrap"))
            SyntaxHilighting = defaultOrValue(true, tryParseBool(getValue editorElement "SyntaxHilighting"))
            ConvertTabsToSpaces = defaultOrValue(true, tryParseBool(getValue editorElement "ConvertTabsToSpaces"))
            ShowSpace = defaultOrValue(true, tryParseBool(getValue editorElement "ShowSpace"))
            ShowTabs = defaultOrValue(true, tryParseBool(getValue editorElement "ShowTabs"))
            ShowEndOfLine = defaultOrValue(true, tryParseBool(getValue editorElement "ShowEndOfLine"))
            ShowLineNumber = defaultOrValue(true, tryParseBool(getValue editorElement "ShowLineNumber"))                                
        }
                
    type InteractiveOption =
        {
             FSIOptions     : string (* comma seperated value *)
             FSIPath        : string (* full path of fsi.exe *)
             EditorOption   : EditorOption
        }
        static member private InteractiveSettingsFilePath = Path.Combine(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "Refunctor"),"InteractiveSettings.xml")
        static member private DefaultFsiPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles)+ @"\Microsoft F#\v4.0\Fsi.exe")
        static member Default =           
            {
                FSIOptions = "--optimize"
                FSIPath = InteractiveOption.DefaultFsiPath
                EditorOption = 
                {
                    FontName = "Consolas"
                    FontSize = 14.0
                    FontColor = Colors.Black
                    BackgroundColor = Colors.White
                    IdentSize = 4.0
                    WordWrap = true
                    SyntaxHilighting = true
                    ConvertTabsToSpaces = true
                    ShowSpace = false
                    ShowTabs = false
                    ShowEndOfLine = false
                    ShowLineNumber = false                                
                }
            }
        member self.Save () =
            let addValues (rootElement:XElement) =
                rootElement.Add(new XElement(XName.Get("FsiOption"), self.FSIOptions))
                rootElement.Add(new XElement(XName.Get("FsiPath"), self.FSIPath))
                let editorElement = creatEditorElement(self.EditorOption)
                rootElement.Add(editorElement)
            
            if not <| File.Exists(InteractiveOption.InteractiveSettingsFilePath) then
                let dirPath = Path.GetDirectoryName(InteractiveOption.InteractiveSettingsFilePath)
                if not <| Directory.Exists(dirPath) then Directory.CreateDirectory(dirPath) |> ignore
                let xdoc = new XDocument(Declaration = XDeclaration("1.0", "utf-8", "yes"));
                let rootElement = new XElement(XName.Get("Settings")) 
                addValues rootElement           
                xdoc.Add(rootElement)
                xdoc.Save(InteractiveOption.InteractiveSettingsFilePath)
            else            
                let xdoc = XDocument.Load(InteractiveOption.InteractiveSettingsFilePath)
                let rootElement = xdoc.Root
                rootElement.RemoveNodes()
                addValues rootElement   
                xdoc.Save(InteractiveOption.InteractiveSettingsFilePath)  
        
        static member Load () : InteractiveOption =             
            if not <| File.Exists(InteractiveOption.InteractiveSettingsFilePath) then InteractiveOption.Default
            else
                try
                    let xdoc = XDocument.Load(InteractiveOption.InteractiveSettingsFilePath)                   
                    let rootElement = xdoc.Elements() |> Seq.tryPick (fun x -> if x.Name.LocalName = "Settings" then Some(x) else None)                                  
                    {
                        FSIOptions = defaultOrValue("--optimize", getValue rootElement "FsiOption")
                        FSIPath = defaultOrValue (InteractiveOption.DefaultFsiPath, getValue rootElement "FsiPath")
                        EditorOption = createEditorOption rootElement                        
                    }
                with
                  | _ -> 
                    try
                        File.Delete(InteractiveOption.InteractiveSettingsFilePath)
                    with _ -> ()
                    InteractiveOption.Default

    type ProgramEditorOption =
        {
             FSCPath                    : string (* full path of fsc.exe *)
             SourceFilePath             : string
             FSCOptions                 : string (* comma seperated value *)
             OutputPath                 : string                          
             LoadToReflectorAfterBuild  : bool
             EditorOption               : EditorOption
        }
        static member private ProgramEditorSettingsFilePath = Path.Combine(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "Refunctor"),"ProgramEditorSettings.xml")
        static member internal DefaultFscPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles)+ @"\Microsoft F#\v4.0\Fsc.exe")
        static member internal DefaultSaveFilePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData)+ @"\Refunctor\Programs")
        static member internal DefaultOutputPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData)+ @"\Refunctor\Output")
        static member internal DefaultFscOption = " -g --debug:full --target:exe --utf8output "
        
        static member Default =           
            {
                FSCOptions = ProgramEditorOption.DefaultFscOption
                FSCPath = ProgramEditorOption.DefaultFscPath
                SourceFilePath = ProgramEditorOption.DefaultSaveFilePath
                OutputPath = ProgramEditorOption.DefaultOutputPath
                LoadToReflectorAfterBuild = true
                EditorOption = 
                {
                    FontName = "Consolas"
                    FontSize = 14.0
                    FontColor = Colors.Black
                    BackgroundColor = Colors.White
                    IdentSize = 4.0
                    WordWrap = false
                    SyntaxHilighting = true
                    ConvertTabsToSpaces = true
                    ShowSpace = false
                    ShowTabs = false
                    ShowEndOfLine = false
                    ShowLineNumber = false                                
                }
            }
        
        member self.Save () =
            let addValues (rootElement:XElement) =
                rootElement.Add(new XElement(XName.Get("FscOption"), self.FSCOptions))
                rootElement.Add(new XElement(XName.Get("FscPath"), self.FSCPath))
                rootElement.Add(new XElement(XName.Get("SourceFilePath"), self.SourceFilePath))
                rootElement.Add(new XElement(XName.Get("OutputPath"), self.OutputPath))
                rootElement.Add(new XElement(XName.Get("LoadToReflectorAfterBuild"), self.LoadToReflectorAfterBuild))
                let editorElement = creatEditorElement self.EditorOption
                rootElement.Add(editorElement)
            
            if not <| File.Exists(ProgramEditorOption.ProgramEditorSettingsFilePath) then
                let dirPath = Path.GetDirectoryName(ProgramEditorOption.ProgramEditorSettingsFilePath)
                if not <| Directory.Exists(dirPath) then Directory.CreateDirectory(dirPath) |> ignore
                let xdoc = new XDocument(Declaration = XDeclaration("1.0", "utf-8", "yes"));
                let rootElement = new XElement(XName.Get("Settings")) 
                addValues rootElement           
                xdoc.Add(rootElement)
                xdoc.Save(ProgramEditorOption.ProgramEditorSettingsFilePath)
            else            
                let xdoc = XDocument.Load(ProgramEditorOption.ProgramEditorSettingsFilePath)
                let rootElement = xdoc.Root
                rootElement.RemoveNodes()
                addValues rootElement   
                xdoc.Save(ProgramEditorOption.ProgramEditorSettingsFilePath) 
        
        static member Load () : ProgramEditorOption =             
            if not <| File.Exists(ProgramEditorOption.ProgramEditorSettingsFilePath) then ProgramEditorOption.Default
            else
                try
                    let xdoc = XDocument.Load(ProgramEditorOption.ProgramEditorSettingsFilePath)                   
                    let rootElement = xdoc.Elements() |> Seq.tryPick (fun x -> if x.Name.LocalName = "Settings" then Some(x) else None)                                  
                    {
                        FSCOptions = defaultOrValue(ProgramEditorOption.DefaultFscOption, getValue rootElement "FscOption")
                        FSCPath = defaultOrValue (ProgramEditorOption.DefaultFscPath, getValue rootElement "FscPath")
                        SourceFilePath = defaultOrValue (ProgramEditorOption.DefaultSaveFilePath, getValue rootElement "SourceFilePath")
                        OutputPath = defaultOrValue (ProgramEditorOption.DefaultOutputPath, getValue rootElement "OutputPath")
                        LoadToReflectorAfterBuild = defaultOrValue (true, tryParseBool(getValue rootElement "LoadToReflectorAfterBuild"))
                        EditorOption = createEditorOption rootElement
                    }
                with
                  | _ -> 
                    try
                        File.Delete(ProgramEditorOption.ProgramEditorSettingsFilePath)
                    with _ -> ()
                    ProgramEditorOption.Default
